package eu.irreality.dai.world.level;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import eu.irreality.dai.gameplay.entities.Entity;
import eu.irreality.dai.gameplay.world.LevelDescriptor;
import eu.irreality.dai.ui.cell.DisplayableObject;
import eu.irreality.dai.util.Position;
import eu.irreality.dai.util.SortedList;

/**
 * Root class for the hierarchy of levels.
 * 
 */
public abstract class Level
{

    @SuppressWarnings("unchecked")
    protected SortedList[][] grid;

    private List<Entity> entities = new LinkedList<Entity>();

    private LevelDescriptor descriptor;

    private boolean[][] seen;

    public boolean[][] getSeen()
    {
	return seen;
    }

    public void markPositionAsSeen(Position p, boolean value)
    {
	seen[p.getRow()][p.getCol()] = value;
    }

    public boolean isSeen(Position p)
    {
	return seen[p.getRow()][p.getCol()];
    }

    public void setDescriptor(LevelDescriptor ld)
    {
	descriptor = ld;
    }

    public LevelDescriptor getDescriptor()
    {
	return descriptor;
    }

    /**
     * Returns the first up (down) stairs which haven't been assigned a
     * destination position.
     * 
     * @param up
     * @return
     */
    /*
     * private Stairs getFirstUnassignedStairs(boolean up) { for ( Entity e :
     * entities ) { if ( e instanceof Stairs ) { Stairs s = (Stairs) e; if (
     * s.isUp() == up && !s.isDestinationPositionAssigned() ) return s; } }
     * return null; }
     * 
     * private void twoWayConnect ( Level l , boolean up ) { for ( Entity e :
     * entities ) { if ( e instanceof Stairs ) { Stairs s = (Stairs) e; if (
     * s.isUp() == up && s.getDestinationLevel() == l ) { for ( Entity e2 :
     * l.entities ) { if ( e2 instanceof Stairs ) { Stairs s2 = (Stairs) e2; if
     * ( s2.isUp() != up && s2.getDestinationDescriptor() ==
     * this.getDescriptor() ) //note that we do not compare levels but
     * descriptors here //comparing levels would lead to eager generation of the
     * whole graph { s.setDestinationPosition(s2.getPosition());
     * s2.setDestinationPosition(s.getPosition()); } } } } } } }
     */

    /**
     * adds to the list only.
     */
    private void addEntity(Entity e)
    {
	entities.add(e);
    }

    public void removeEntity(Entity e)
    {
	this.remove(e, e.getPosition().getRow(), e.getPosition().getCol());
	entities.remove(e);
    }

    public List<Entity> getEntities()
    {
	return entities;
    }

    /**
     * Default constructor.
     */
    public Level()
    {
    }

    /**
     * Creates a level of size cols x rows.
     * 
     * @param rows
     *            Rows of the level.
     * @param height
     *            Cols of the level.
     */
    public Level(int rows, int cols)
    {
	grid = new SortedList[rows][cols];
	seen = new boolean[rows][cols];
    }

    /**
     * Sets a DisplayableObject on the point (x,y), deleting any other existing
     * objects present at that point.
     * 
     * @param c
     *            DisplayableObject to be set
     * @param x
     *            x coordinate
     * @param y
     *            y coordinate
     */
    public void set(DisplayableObject c, int x, int y)
    {
	SortedList cur = (SortedList) grid[x][y];
	cur.clear();
	cur.add(c);
    }

    /**
     * Adds a DisplayableObject on the point (x,y).
     * 
     * @param c
     *            DisplayableObject to be added
     * @param x
     *            x coordinate
     * @param y
     *            y coordinate
     */
    public void add(DisplayableObject c, int x, int y)
    {
	SortedList cur = (SortedList) grid[x][y];
	cur.add(c);
    }

    public void addEntity(Entity e, int row, int col)
    {
	e.setPosition(row, col);
	add(e, row, col);
	addEntity(e);
    }

    /**
     * Removes a DisplayableObject on the point (row,col).
     * 
     * @param c
     *            DisplayableObject to be removed
     * @param x
     *            row coordinate
     * @param y
     *            col coordinate
     */
    public void remove(DisplayableObject c, int row, int col)
    {
	SortedList cur = (SortedList) grid[row][col];
	cur.remove(c);
    }

    /**
     * Gets the list of DisplayableObjects on the point (x,y).
     * 
     * @param x
     *            x coordinate
     * @param y
     *            y coordinate
     * @return DisplayableObject on the point (row,col).
     */
    public SortedList get(int row, int col)
    {
	return grid[row][col];
    }

    /**
     * Gets the list of DisplayableObjects on a given Position.
     * 
     * @param Position
     *            .
     * @return DisplayableObject on the given Position.
     */
    public SortedList get(Position pos)
    {
	return grid[pos.getRow()][pos.getCol()];
    }

    /**
     * Gets the grid of lists of DisplayableObjects.
     * 
     * @return Grid of SortedLists containing DisplayableObjects.
     */
    public SortedList[][] getGrid()
    {
	return grid;
    }

    /**
     * Sets the grid of lists of DisplayableObjects.
     * 
     * @param grid
     *            Grid of SortedLists of DisplayableObjects to be set.
     */
    public void setGrid(SortedList[][] grid)
    {
	this.grid = grid;
    }

    /**
     * Gets the width of the level.
     * 
     * @return The width of the level.
     */
    public int getCols()
    {
	return grid[0].length;
    }

    /**
     * Gets the height of the level.
     * 
     * @return The height of the level.
     */
    public int getRows()
    {
	return grid.length;
    }

    @SuppressWarnings("unchecked")
    @Override
    public String toString()
    {
	StringBuilder builder = new StringBuilder();
	for (int i = 0; i < getRows(); i++)
	{
	    for (int j = 0; j < getCols(); j++)
	    {
		SortedList dispObjs = grid[i][j];
		for (Iterator<DisplayableObject> iter = dispObjs.iterator(); iter
			.hasNext();)
		{
		    DisplayableObject dispObj = iter.next();
		    builder.append(dispObj.getChar());
		}
	    }
	    builder.append('\n');
	}
	return builder.toString();
    }
}
